const Route = require("./route");

const Layer = require("./layer");

var proto = (module.exports = function (params) {
  function router(req, res, next) {
    router.handle(req, res, next);
  }

  router.stack = [];
  router.params = {};

  Object.setPrototypeOf(router, proto);

  return router;
});

proto.handle = function (req, res, out) {
  let idx = 0;

  let self = this;

  let paramCalled = {};

  const stack = this.stack;
  next();

  function next(err) {
    let match, layer, route;

    while (match !== true && idx < stack.length) {
      layer = stack[idx++];
      match = layer.match(req.url);
      route = layer.route;
      if (!match) continue;
      if (route) {
        let has_method = route.handle_method(req.method);
        if (!has_method) match = false;
      }
    }

    if (!match) return out();

    req.params = layer.params;

    self.process_param(layer, paramCalled, req, res, function () {
      if (err) {
        layer.handle_error(err, req, res, next);
      } else {
        layer.handle_request(req, res, next);
      }
    });
  }
};

proto.process_param = function (layer, called, req, res, done) {
  const keys = layer.keys;
  const params = this.params;
  if (!keys.length) return done();

  let idx = 0,
    paramValue,
    paramIndex = 0,
    paramCallbacks;

  param();
  function param() {
    if (idx >= keys.length) return done();
    const key = keys[idx++];
    paramValue = layer.params[key];
    paramCallbacks = params[key];
    if (paramCallbacks === undefined) {
      return param();
    }
    if (called[key]) {
      return param();
    } else {
      called[key] = {
        value: paramValue,
      };
      paramIndex = 0;
      paramCallback();
    }
  }

  function paramCallback() {
    const fn = paramCallbacks[paramIndex++];
    if (!fn) return param();
    fn(req, res, paramCallback, paramValue);
  }
};

proto.route = function (path) {
  const route = new Route(path);
  const layer = new Layer(path, {}, route.dispatch.bind(route));
  layer.route = route;
  this.stack.push(layer);
  return route;
};

proto.get = function (path, ...args) {
  const route = new Route(path);
  route.get(...args);
  return this;
};

proto.param = function (name, callback) {
  console.log("🚀 ～ file: index.js:106 ～ callback:", callback);
  if (Array.isArray(this.params[name])) {
    this.params[name].push(callback);
  } else {
    this.params[name] = [callback];
  }
  console.log(
    "🚀 ～ file: index.js:108 ～  this.params[name] :",
    this.params[name]
  );
  return this;
};

proto.use = function (fn) {
  let offset = 0;
  path = "/";
  if (typeof fn !== "function") {
    path = fn;
    offset = 1;
  }

  const callbacks = [].slice.call(arguments, offset);

  // this.lazyrouter();

  callbacks.forEach((callback) => {
    let layer = new Layer(path, { end: false }, callback);

    layer.route = undefined;

    this.stack.push(layer);
  });
};
